package serviceModel;

import java.beans.XMLDecoder;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.*;
import java.io.File;
import static java.nio.file.StandardWatchEventKind.*;
import static java.nio.file.LinkOption.*;
import java.nio.file.attribute.*;
import java.util.HashMap;
import java.util.zip.Adler32;
import java.util.zip.CheckedInputStream;
import java.util.zip.Checksum;
import XML.UserXML;
import clientRequest.ClientRequest;

/**
 *This class responsible to listen to changes in the operation system
 *and to create a request with all the details of the change in order to send it
 *to the sever  
 */
//contact with the server  if there was a change in the dropbox folder
// or the server updates it about un-synchronization
public class ServiceOSListener {
	
	private static ServiceOSListener serviceClientSingleton = new ServiceOSListener();
	private WatchService watcher; //file system watcher
	private HashMap<WatchKey,Path> keys; //
    
	// static instance of the service class
	//private static ServiceOSListener serviceInstance = new ServiceOSListener();
	   
	
	private boolean stopped;// flag - if service instance has stopped 
	private boolean isMyDropBox;//flag- if the change occur in the dropbox folder 
	private boolean isFolder; //flag- if the change occur on a folder or a file
	private static String dropBoxPath; //the path of the dropbox folder
	private static Thread requestSender; // an instance of the class which pass the requests to the server
	private static Thread sharedFilesThread; //an instance of the class which updates about un-synchronization with the server 
	//private static boolean isFirstTime=true;
	//private static Registry registry;
	//private TagFile tagFile;
	private static String userName; //the user name
	private static String userXmlPath = new String("C:\\clever dropbox client\\userXML.xml");

	/**
	 * default constructor
	 */
	 public ServiceOSListener() {
	  
		  try {
		   this.watcher = FileSystems.getDefault().newWatchService();
		   this.keys = new HashMap<WatchKey,Path>();
		  } catch (IOException e) {}
		  
	 }
	
	 private void registerPath(String path)
	 {
		 //register all the sub folder 
		   try {
		    Path dir = Paths.get(path);
		    registerAll(dir);
		  } catch (IOException e) {}
	 }
	 
	 
	 private void register(Path dir)
	 {
		 	//register the directory to be watched
		   try {
		    WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
		    keys.put(key, dir);
		  } catch (IOException e) {}
	 }
	 
	 /**
	 * Register the given directory, and all its sub-directories, with the
	 * WatchService.
	 */
	private void registerAll(final Path start) throws IOException {
	    // register directory and sub-directories
	        Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
	            @Override
	            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
	                throws IOException
	            {
	                register(dir);
	                return FileVisitResult.CONTINUE;
	            }
	        });
	    }
	 
	/**
	 * This method listen to changes in a directory (and all its sub- directories) and 
	 * prepare request with all the details on the change and send it to the server. 
	 */
	 public void listenToEvents()
	 {
	  WatchKey key; 
	  
	  //while the service has'nt stopped
	  while (!stopped)
	  {   
		  
	            try {
	                key = watcher.take();
	            } catch (InterruptedException x) {
	                continue;
	            }
	            
	            Path dir = keys.get(key);
	            
	            if (dir==null)
	             continue;
	            
	            for (WatchEvent<?> event: key.pollEvents()) {
	                WatchEvent.Kind kind = event.kind();
	
	                // TBD - provide example of how OVERFLOW event is handled
			    if (kind == OVERFLOW) {
			        continue;
			    }        
	
		    // Context for directory entry event is the file name of entry
		    WatchEvent<Path> ev = (WatchEvent<Path>) event;
		    
		    //get the name of the file that was change
		    Path name = ev.context();
		    
		    //get the path of the file that was change
		    Path child = dir.resolve(name);
		    
		    String childStr=child.toString();
		    String s=childStr.substring(0,childStr.lastIndexOf("\\"));
		    
		    System.out.println("-----ssss-----"+s);
		    
		    //checks if the change occur in the dropbox folder or in inner folder
		    if (s.equals(dropBoxPath))
		    {
		    	
		    	System.out.println("------IN MY DROPBOX----");
		    	isMyDropBox=true;
		    }
		    else
		    {
		    	System.out.println("------NOT IN MY DROPBOX----");
		    	isMyDropBox=false;
		    }
		    
		    
		    System.out.println("child:"+child.toString());
		    
		    try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		    
			
		    //checks if the change occurred on a file or a folder
		    isFolder=Files.isDirectory(child, NOFOLLOW_LINKS);
		    
		    //the file that was changed
		    File someFile = new File(child.toString());
		    		    
		    if(isFolder)
		    	System.out.println("IM FOLDER");
		    else
		    	System.out.println("IM NOT");
		    
		    byte [] bytesFile=null;
	
	        long checksum=0;
		    
	        System.out.println("kind "+kind);
	        
		    if ((someFile.exists())&&((kind == ENTRY_CREATE) || (kind == ENTRY_MODIFY))&&!isFolder){
		    	
		    	System.out.println("here1");
		    //	tagFile.updateTagFile(childStr);
		    	
		    	//convert the file to bytes
		    	bytesFile=convFileToByte(someFile);
		    	
		    	//get an hash to the file
		    	checksum = getChecksum(childStr);
		    	
		    }
		    
		    
		    else if ((kind == ENTRY_DELETE)&&!isFolder){
		    	System.out.println("here2");
		    	System.out.println("OS---deleting file");
		    	//tagFile.deleteTagFile(childStr);
		    }
		   
	
		    //the request to the server
		    ClientRequest request;
		    
		    //creates the request with all the details
		    if(!isFolder)
			    request=new ClientRequest(userName,kind.name(),name.toString(),child.toString(),bytesFile,checksum,false,isMyDropBox); 
		    
		    else
		    	request=new ClientRequest(userName,kind.name(),name.toString(),child.toString(),bytesFile,0,true,isMyDropBox);
		    
		    //avoid notifying the server of a change of a file that is under a folder that was deleted 
		    if(kind!=ENTRY_MODIFY||someFile.exists()){
		    	
			    ClientRequestSender.getInstance().AddRequestToQueue(request);
			    request.printRequest();
		    }
	   
		    System.out.format("%s %s: %s", event.kind().name(), name, child);//,isFolder,isMyDropBox);
		    
	    
		    // if directory is created and there are already files in it, creates a request to each file
		    // in order to updates the server 
		    if (kind == ENTRY_CREATE) {
		        try {
		            if (isFolder) {
		            	System.out.println("here3");
		            	File[]files=someFile.listFiles();
		            	
		            	//loop over all the files and sub directories in the new folder
		            	for(int i=0;i<files.length;i++){
		            		File file=files[i];
		            		
		            		//if it is a file
		            		if(file.isFile()){
		            			//convert the file to bytes
		            			bytesFile=convFileToByte(file);
		            			//gets an hash to the file
		            	    	checksum = getChecksum(file.getPath());
		            	    	//prepare a request to the server
		            			request=new ClientRequest(userName,kind.name(),file.getName(),file.getPath(),bytesFile, checksum,false,false);
		            			ClientRequestSender.getInstance().AddRequestToQueue(request);
		            			request.printRequest();
		            			
		            		}
		            	}
		             System.out.println("----folder-----"+child);
		          // if directory is created, and watching recursively, then
		     	    // register it and its sub-directories
		                registerAll(child);
		            }
		        } catch (IOException x) {
		            // ignore to keep sample readable
		        }
		    }
		    
		    
		 // reset key and remove from set if directory no longer accessible
		    boolean valid = key.reset();
		    if (!valid) {
		        keys.remove(key);
		
		        // all directories are inaccessible
		                    if (keys.isEmpty()) {
		                        break;
		                    }
		                }
		            }
		   
		  } //end while
	  
	  //stop the running of both threads- from the client to the server and back
	  ClientRequestSender.getInstance().stopRun();
	  GetSharedFiles.getInstance().stopRun();
	  
	 }
	  
	 
	 //converts a File to a bytes array
	 private byte[] convFileToByte(File file){
		  FileInputStream fis = null;
		  try {
			   fis = new FileInputStream(file);
			  } catch (FileNotFoundException e1) {
			   // TODO Auto-generated catch block
		   e1.printStackTrace();
		  }
		
		  ByteArrayOutputStream bos = new ByteArrayOutputStream();
		  byte[] buf = new byte[1024];
		  try {
		      for (int readNum; (readNum = fis.read(buf)) != -1;) {
		          bos.write(buf, 0, readNum); //no doubt here is 0
		  //Writes len bytes from the specified byte array starting at offset off to this byte array output stream.
		      }
		  } catch (IOException ex) {
		     
		  }
		  byte[] bytes = bos.toByteArray();
		  
		  try {
		   fis.close();
		   bos.flush();
		   bos.close();
		  } catch (IOException e) {
		   // TODO Auto-generated catch block
			   e.printStackTrace();
			  }
			  return bytes;
			 
	 }
	
	 //get a hash to a File-according to its context
	 private long getChecksum(String filePath)
	 {
		  FileInputStream is = null;
		  CheckedInputStream cis = null;
		  Checksum checksum = new Adler32();
	
	     try 
	     {
	   	  is = new FileInputStream(filePath);
	         cis = new CheckedInputStream(is, checksum);
	         byte[] tempBuf = new byte[128];
	         while (cis.read(tempBuf) >= 0) {}                
	     }
	     catch (Exception e)
	     {
	         throw new RuntimeException(e);
	     }
	     finally
	     {
	         if (cis != null)
	         {
	             try
	             {
	                 cis.close();
	             }
	             catch (IOException ioe) {}
	             cis = null;
	         }
	         if (is != null)
	         {
	             try
	             {
	                 is.close();
	             }
	             catch (IOException ioe) {}
	             is = null;
	         }
	     }
	     return checksum.getValue();
	 }
	
	//read from XML
	 private static UserXML readXML(String xmlPath)
		{
		
			FileInputStream file;
			UserXML userXML = null;
			XMLDecoder decoder = null;
			//connection to the file. if FileInputStream throw an exception - the upper function handle it
			try {
				file = new FileInputStream(xmlPath);
				
				decoder = new XMLDecoder(file);
			} catch (FileNotFoundException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
				return null;
			}	
		
			/*read the object. if decoder.readObject throw an exception - need to close 
			  the file and the upper function handle it*/
			try
			{
				userXML = (UserXML) decoder.readObject();
			}
			catch (Exception e)
			{
				try {
					decoder.close();
					file.close();
				} catch (IOException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
					return null;
				}
			}
			
			//close connection
			decoder.close();
			try {
				file.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	
			return userXML;
	
		}
	
	
	
	public static void main(String[] args)
	{
		
		 //********************************************************
		  // after getting from the user the path of his folder
		 ServiceOSListener myService = new ServiceOSListener();
		 //if(myService.isFirstTime) 
		 //GUIService guiService;
		/* Thread thread = new Thread(new Runnable() {
		   
		   @Override
		   public void run() {
		    // TODO Auto-generated method stub
		     GUIService.GUIServiceMain();
		    
		   }
		  });
		   thread.start();
		   try {
		   thread.join();
		  } catch (InterruptedException e) {
		   // TODO Auto-generated catch block
		   e.printStackTrace();
		  }*/
		  
		  //GUIService.GUIServiceMain();
		  //dropBoxPath = GUIService.getDropBoxPath();
		  //userName = GUIService.getUserName();
		  UserXML userXML=readXML(userXmlPath);
		  if (userXML!=null){
			  dropBoxPath=userXML.getDropboxPath();
			  userName = userXML.getUserID();
			  
			  requestSender = new Thread(ClientRequestSender.getInstance());
			  requestSender.start();
			  
			  GetSharedFiles.getInstance().setUserID(userName);
			  GetSharedFiles.getInstance().setDropboxPath(dropBoxPath);
			  sharedFilesThread = new Thread(GetSharedFiles.getInstance());
			  sharedFilesThread.start();
			  
		
			   myService.registerPath(dropBoxPath);
			   myService.listenToEvents();
		  }
	}
		
	//service.listenToEvents();
	/**
	 * This method start the program
	 */
	public static void windowsService(String[] args)
	{
		
		 //ServiceOSListener service = new ServiceOSListener();
		 System.out.println("====windowsService===");
		 String cmd = "start";
		 if(args.length > 0) cmd = args[0];
	
		 if("start".equals(cmd))  serviceClientSingleton.start();
		 else serviceClientSingleton.stop();
	}
	
	 /**
	  * This method starts the service 
	  */
	 public void start() {
		
		 System.out.println("**********************START**************");
		 stopped = false;
		
		 //read the user XML
		 UserXML userXML=readXML(userXmlPath);
		 
		 //if the user already did the installation
		 if (userXML!=null){
			 
			 System.out.println("NOTNULL");
			 //gets the user dropbox's path 
			  dropBoxPath=userXML.getDropboxPath();
			  
			  //gets the user's id
			  userName = userXML.getUserID();
			  
			  //creates a thread to pass requests from the client to the sever
			 if(requestSender==null||!requestSender.isAlive()){
				 requestSender = new Thread(ClientRequestSender.getInstance());
				 requestSender.start();
			 }
			 
			 //creates a thread to pass messages from the server to the client
			 if(sharedFilesThread==null||!sharedFilesThread.isAlive()){
				 GetSharedFiles.getInstance().setUserID(userName);
				 GetSharedFiles.getInstance().setDropboxPath(dropBoxPath);
			  sharedFilesThread = new Thread(GetSharedFiles.getInstance());
			  sharedFilesThread.start();
			 }
			
			 //register the dropbox to be watched
			 registerPath(dropBoxPath);
			 
			 //start listening to changes in the dropbox
			 listenToEvents();
			
		 
		 }
		 else
			 System.out.println("NULL");
	
	 }
	
	 /**
	  * this method stops this service 
	  */
	  
	 public void stop() {
		 
		 System.out.println("**********************STOP**************");
	    stopped = true;
	    synchronized(this) {
	       this.notify();
	    }
	 }
	 
	 
	 
	}
	
	 

